package org.genmymodel.common.api; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import org.apache.commons.io.FileUtils; import org.genmymodel.common.account.GMMCredential; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; /** * This class provides facilities to communicate with GenMyModel API in order to * compile/execute a custom generator project. * * @author Vincent Aranega * @author Ali Gourch * */ public class GMMAPIRestClient { public static final String REAL_API = "https://api.genmymodel.com"; public static final String OAUTH_TOK = REAL_API + "/oauth/token"; public static final String USER_PROJECTS = REAL_API + "/users/{username}/projects"; public static final String USER_PROJECT = REAL_API + "/projects/{projectID}"; public static final String USER_SHARED_PROJECTS = REAL_API + "/projects/shared"; public static final String USER_CUSTOMGENERATORS = REAL_API + "/customgenerators"; public static final String USER_CUSTOMGENERATOR = REAL_API + "/customgenerators/{generatorID}"; public static final String COMPILE_RESTURL = REAL_API + "/customgenerators/dev/compile"; public static final String EXEC_RESTURL_FRAG = REAL_API + "/customgenerators/dev/execute/"; public static final String USER_IMPORTED_PROJECT = REAL_API + "/projects/import"; public static final String CUSTOMGEN_CLASSIC_LAUNCH = REAL_API + "/projects/{projectID}/custom/{generatorID}"; public static final String PROJECT_XMI = USER_PROJECT + "/xmi"; private static final String CLIENT_ID = "test"; private static final String CLIENT_SECRET = "test"; public static ThreadLocal<GMMAPIRestClient> THREAD_LOCAL = new ThreadLocal<GMMAPIRestClient>() { @Override protected GMMAPIRestClient initialValue() { return createAPIRestClient(); } }; /** * Prevents external instance creation. */ private GMMAPIRestClient() { } /** * Creates a GMMAPIRestClient (singleton value). * @return A new GMMAPIRestClient. */ protected static GMMAPIRestClient createAPIRestClient() { return new GMMAPIRestClient(); } /** * Gets the GMMAPIRestClient instance. * @return The GMMAPIRestClient instance. */ public static GMMAPIRestClient getInstance() { return THREAD_LOCAL.get(); } /** * Calls the GenMyModel API in order to compile the * custom generator project given as a zip archive. * @param zipArchive The custom generator project to compile. * @return A compilCallResult with information about the compilation. * @throws IOException If an error occurs during unpacking of answered zip. */ public CompilCallResult POSTCompile(File zipArchive) throws IOException { return POST(new RestTemplate(), COMPILE_RESTURL, zipArchive); } /** * Calls the GenMyModel API in order to execute the compiled custom generator * project given as a zip archive. * @param zipArchive The compiled custom generator project to execute * @param projectID The model project ID on which the generator should be executed. * @param credential The user credential to access the projectID. * @return A CompilationCallResult with information about the execution. * @throws IOException If an error occurs during unpacking of the answered zip. */ public CompilCallResult POSTExec(File zipArchive, String projectID, GMMCredential credential) throws IOException { return POST(createOAuthTemplate(credential), EXEC_RESTURL_FRAG + projectID, zipArchive); } public CompilCallResult POSTCustomgenLaunch(String projectID, String customgenID, GMMCredential credential) throws IOException { return POST(createOAuthTemplate(credential), CUSTOMGEN_CLASSIC_LAUNCH, projectID, customgenID); } public CompilCallResult POST(RestTemplate template, String url, Object... urlVariables) throws IOException { try { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<String> entity = new HttpEntity<String>("",headers); ResponseEntity<GMMCallResult> res = template.postForEntity(url, entity, GMMCallResult.class, urlVariables); if (res.getBody().getOutputUrl() == null) { return new CompilCallResult(res.getBody(), null); } File tmpZip = File.createTempFile("GMM", ".zip"); FileUtils.copyURLToFile(new URL(res.getBody().getOutputUrl()), tmpZip, 10000, 10000); return new CompilCallResult(res.getBody(), tmpZip); } catch (HttpStatusCodeException e) { GMMCallResult call = new ObjectMapper().readValue(e.getResponseBodyAsString(), GMMCallResult.class); return new CompilCallResult(call, null); } } public void PUTCustomGen(CustomGeneratorBinding generator, GMMCredential credential) throws IOException { PUT(createOAuthTemplate(credential), USER_CUSTOMGENERATOR, generator, generator.getGeneratorId()); } public <T> void PUT(RestTemplate template, String url, T object, Object... urlVariables) throws IOException { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<T> entity = new HttpEntity<T>(object, headers); template.put(url, entity, urlVariables); } private CompilCallResult POST(RestTemplate template, String url, File zipArchive) throws IOException { Resource resource = new FileSystemResource(zipArchive); MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE); parts.add("file", resource); try { ResponseEntity<GMMCallResult> res = template.exchange(url, HttpMethod.POST, new HttpEntity<MultiValueMap<String, Object>>(parts), GMMCallResult.class); File tmpZip = File.createTempFile("GMM", ".zip"); FileUtils.copyURLToFile(new URL(res.getBody().getOutputUrl()), tmpZip, 10000, 10000); return new CompilCallResult(res.getBody(), tmpZip); } catch (HttpStatusCodeException e) { GMMCallResult call = new ObjectMapper().readValue(e.getResponseBodyAsString(), GMMCallResult.class); return new CompilCallResult(call, null); } } /** * Calls the GenMyModel API in order to import the user project. * @param credential The user credential. * @param project The user project. * @return A ResponseEntity containing the user project informations. */ public ResponseEntity<ProjectPostBinding> POSTImportedProject(GMMCredential credential, ProjectPostBinding project) { ResponseEntity<ProjectPostBinding> response = createOAuthTemplate(credential).postForEntity(USER_IMPORTED_PROJECT, project, ProjectPostBinding.class); return response; } /** * Calls the GenMyModel API in order to create a new user custom generator. * @param credential The user credential. * @param generator The user generator. * @return A ResponseEntity containing the user generator informations. */ public ResponseEntity<CustomGeneratorBinding> POSTGenerator(GMMCredential credential, CustomGeneratorBinding generator) { try { ResponseEntity<CustomGeneratorBinding> response = createOAuthTemplate(credential).postForEntity(USER_CUSTOMGENERATORS, generator, CustomGeneratorBinding.class); return response; } catch (HttpStatusCodeException e) { e.printStackTrace(); return null; } } /** * Calls the GenMyModel API in order to delete the user project. * @param credential The user credential. * @param projectID the project ID. */ public void DELETEProject(GMMCredential credential, String projectID) { createOAuthTemplate(credential).delete(USER_PROJECT, projectID); } /** * Returns user projects. * @param credential The user credential. * @return A tab containing the user project informations. */ public ProjectBinding[] GETMyProjects(GMMCredential credential) { return GET(USER_PROJECTS, ProjectBinding[].class, credential, credential.getUsername()); } /** * Returns shared user projects. * @param credential The user credential. * @return A tab containing the shared user project informations. */ public ProjectBinding[] GETSharedProjects(GMMCredential credential) { return GET(USER_SHARED_PROJECTS, ProjectBinding[].class, credential, credential.getUsername()); } /** * Returns project XMI. * @param credential The user credential. * @param projectID The project ID. * @return A String containing the project XMI. */ public String GETProjectXMI(GMMCredential credential, String projectID) { return GET(PROJECT_XMI, String.class, credential, projectID); } /** * Returns user custom generators. * @param credential The user credential. * @return A tab containing the user project informations. */ public CustomGeneratorBinding[] GETMyCustomGenerators(GMMCredential credential) { return GET(USER_CUSTOMGENERATORS, CustomGeneratorBinding[].class, credential, credential.getUsername()); } /** * Calls the GenMyModel API in order to delete the user generator. * @param credential The user credential. * @param generatorID the generator ID. */ public void DELETEGenerator(GMMCredential credential, Integer generatorID) { createOAuthTemplate(credential).delete(USER_CUSTOMGENERATOR, generatorID); } /** * Performs a GET call on a given address. * @param url The URL to call. * @param clazz The answer type. * @param credential The user credential. * @param params Additional parameter that could be part of the URL. * @return An answer of clazz type. */ public <T> T GET(String url, Class<T> clazz, GMMCredential credential, Object... params) { RestTemplate template = createOAuthTemplate(credential); ResponseEntity<T> response = template.getForEntity(url, clazz, params); return response.getBody(); } /** * Performs a GET call on a given address and return the body as an InputStream. * @param url The URL to call. * @param credential The user credential, if null, a non oauth rest template is used. * @param params Additional parameter that could be part of the URL. * @return An InputStream. */ public InputStream GETasInputstream(String url, GMMCredential credential, Object... params) { RestTemplate template = credential != null ? createOAuthTemplate(credential): new RestTemplate(); ResponseEntity<byte[]> res = template.getForEntity(url, byte[].class, params); return new ByteArrayInputStream(res.getBody()); } /** * Creates an OAuth2 rest template that provides facilities to deal with OAuth2 auth. * @param credential The user credential. * @return A new OAut2 Rest Template. */ protected RestTemplate createOAuthTemplate(GMMCredential credential) { ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); details.setClientId(CLIENT_ID); details.setClientSecret(CLIENT_SECRET); details.setUsername(credential.getUsername()); details.setPassword(credential.getPassword()); details.setAccessTokenUri(OAUTH_TOK); OAuth2RestTemplate template = new OAuth2RestTemplate(details); template.getMessageConverters().add(new FormHttpMessageConverter()); template.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); return template; } /** * Represents a compilation/execution wrapper. * * @author Vincent Aranega * */ public class CompilCallResult { /** * Compilation call result. */ public GMMCallResult callResult; /** * Produced zip file downloaded if the call result has no error. If an * error occured during compilation/execution, this variable is null. */ public File zip; public CompilCallResult(GMMCallResult res, File zip) { this.callResult = res; this.zip = zip; } } }